调用 PAM 进行认证
1// gcc main.c -lpam
2
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <unistd.h>
7#include <termio.h>
8#include <security/pam_appl.h>
9
10/***************************************
11 * @brief 开关回显
12 * @param[in] fd 文件描述符
13 * @param[in] off 1-关闭回显,0-开启回显
14 * *************************************/
15static void echoOff(int fd, int off)
16{
17 struct termio tty;
18 (void) ioctl(fd, TCGETA, &tty);
19 if (off)
20 {
21 tty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
22 (void) ioctl(fd, TCSETAF, &tty);
23 }
24 else
25 {
26 tty.c_lflag |= (ECHO | ECHOE | ECHOK | ECHONL);
27 (void) ioctl(fd, TCSETAW, &tty);
28 }
29}
30
31/***************************************
32 * @brief 关闭标准输入的回显
33 * *************************************/
34static void echoOffStdin()
35{
36 echoOff(fileno(stdin), 1);
37}
38
39/***************************************
40 * @brief 开启标准输入的回显
41 * *************************************/
42static void echoOnStdin()
43{
44 echoOff(fileno(stdin), 0);
45}
46
47/***************************************
48 * @brief 读取一行输入
49 * @return 输入的字符串
50 * *************************************/
51static char* readline()
52{
53 struct termio tty;
54 char input[PAM_MAX_RESP_SIZE];
55
56 /* 读取字符直到回车 */
57 flockfile(stdin);
58 int i = 0;
59 for (; i < PAM_MAX_RESP_SIZE; i++)
60 {
61 int ch = getchar_unlocked();
62 if (ch == '\n' || ch == '\r' ||ch == EOF)
63 break;
64 input[i] = ch;
65 }
66 funlockfile(stdin);
67 input[i] = '\0';
68
69 return (strdup(input));
70}
71
72/**************************************************
73 * @brief PAM对话回调函数
74 * @param[in] num_msg PAM发送过来的消息数量
75 * @param[in] msg PAM发送过来的消息数据
76 * @param[out] resp 发回给PAM的应答
77 * @param[in] appdata_ptr 用户参数
78 * @return 状态
79 * ************************************************/
80static int conversation(int num_msg, const struct pam_message** msg, struct pam_response **resp, void *appdata_ptr)
81{
82 // 检查参数
83 if (num_msg <= 0 || num_msg >= PAM_MAX_MSG_SIZE)
84 {
85 fprintf(stderr, "invalid num_msg(%d)\n", num_msg);
86 return PAM_CONV_ERR;
87 }
88
89 // 给回复消息分配内存,这里分配的内存,由外层的 PAM 框架释放
90 // TODO: 发生错误的时候需要手动释放
91 if ((resp[0] = malloc(num_msg * sizeof(struct pam_response))) == NULL)
92 {
93 fprintf(stderr, "bad alloc\n");
94 return PAM_BUF_ERR;
95 }
96
97 // 处理PAM发来的消息并应答
98 for(int i = 0; i < num_msg; i++)
99 {
100 const struct pam_message* m = *msg + i;
101 struct pam_response* r = *resp + i;
102 r->resp_retcode = 0; // 这个是保留属性,固定为0
103 switch (m->msg_style)
104 {
105 case PAM_PROMPT_ECHO_OFF: // 请求输入,不回显,例如请求输入用户名
106 printf("%s", m->msg);
107 echoOffStdin(); // 关闭回显
108 r->resp = readline(); // 读取密码
109 echoOnStdin(); // 开启回显
110 printf("\n"); // 补个换行
111 break;
112
113 case PAM_PROMPT_ECHO_ON: // 请求输入,回显,例如请求输入密码
114 printf("%s", m->msg);
115 r->resp = readline();
116 break;
117
118 case PAM_TEXT_INFO: // 打印普通消息
119 printf("%s\n", m->msg);
120 break;
121
122 case PAM_ERROR_MSG: // 打印错误消息
123 fprintf(stderr, "%s\n", m->msg);
124 break;
125
126 default:
127 printf("DEFAULT\n");
128 break;
129 }
130 }
131 return PAM_SUCCESS;
132}
133
134int main()
135{
136 atexit(echoOnStdin); // 退出时开启回显
137
138 struct pam_conv pam_conv = {conversation, NULL};
139 pam_handle_t *pamh;
140 if (PAM_SUCCESS != pam_start("login", NULL, &pam_conv, &pamh))
141 {
142 fprintf(stderr, "pam_start failed\n");
143 return EXIT_FAILURE;
144 }
145
146 if (PAM_SUCCESS != pam_authenticate(pamh, 0))
147 {
148 fprintf(stderr, "pam_authenticate failed\n");
149 pam_end(pamh, 0);
150 return EXIT_FAILURE;
151 }
152
153 pam_end(pamh, 0);
154 return EXIT_SUCCESS;
155}